package com.nx.platform.es.bean.modle.query;

import com.google.common.base.Enums;
import com.google.common.base.Joiner;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Range;
import com.google.common.primitives.Ints;
import com.google.common.primitives.Longs;
import com.nx.platform.es.bean.dto.RequestContext;
import com.nx.platform.es.biz.wrapper.parser.StatementOperator;
import com.nx.platform.es.common.utils.MoreSplitters;
import org.apache.commons.collections4.MapUtils;
import org.elasticsearch.common.Strings;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;

import java.util.List;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

public class ParamsHandler implements QueryFieldHandler {

    private static final Pattern FORMAT_SINGLE = Pattern.compile("^\\d++$");
    private static final Pattern FORMAT_SOME = Pattern.compile("^\\d++(?:\\|\\d++)++$");
    private static final Pattern FORMAT_RANGE = Pattern.compile("^(\\d++|\\*)_(\\d++|\\*)$");

    @Override
    public void handle(ImmutableMap<String, ?> fieldConfig, RequestContext context, String fieldFace,
                       StatementOperator operator, String fieldValue, ListMultimap<Boolean, QueryBuilder> queryBuilders) {
        // 空值
        if (Strings.isNullOrEmpty(fieldValue)) {
            return;
        }
        String[] split = MoreSplitters.COMMA.split(fieldValue);
        Preconditions.checkState(split.length == 2);
        Preconditions.checkState(FORMAT_SINGLE.matcher(split[0]).matches());
        String parentFieldName = MapUtils.getString(fieldConfig, "fieldName");
        Preconditions.checkState(!Strings.isNullOrEmpty(parentFieldName));
        String fieldName = parentFieldName + "." + split[0];
        fieldValue = split[1];
        final String clazz = MapUtils.getString(fieldConfig, "class");
        // 多个值
        if (FORMAT_SOME.matcher(fieldValue).matches()) {
            List<Comparable<? extends Number>> set = MoreSplitters.VERTICAL.splitAsStream(fieldValue)
                    .map(String::trim).map(e -> parse(clazz, e)).filter(Objects::nonNull)
                    .distinct().sorted().collect(Collectors.toList());
            // 记录Query信息(Trace)
            context.getTrace().append("&").append(fieldName)
                    .append(operator.getSymbol()).append(Joiner.on('|').join(set));
            //
            queryBuilders.put(operator.isSign(),
                    QueryBuilders.termsQuery(fieldName, set));
            return;
        }
        // 记录Query信息(Trace)
        context.getTrace().append("&").append(fieldName).append(operator.getSymbol()).append(fieldValue);
        //
        // 单个值
        if (FORMAT_SINGLE.matcher(fieldValue).matches()) {
            queryBuilders.put(operator.isSign(),
                    QueryBuilders.termQuery(fieldName, parse(clazz, fieldValue)));
            return;
        }
        // 区间
        Matcher rangeMatcher = FORMAT_RANGE.matcher(fieldValue);
        if (rangeMatcher.find()) {
            String left = rangeMatcher.group(1);
            String right = rangeMatcher.group(2);
            if (left.equals(right) && !"*".equals(left)) {
                queryBuilders.put(operator.isSign(),
                        QueryBuilders.termQuery(fieldName, parse(clazz, left)));
                return;
            } else if (!"*".equals(left) && !"*".equals(right)) {
                Comparable<? extends Number> ln = parse(clazz, left);
                Comparable<? extends Number> rn = parse(clazz, right);
                try {
                    Range<Comparable<? extends Number>> range = Range.openClosed(ln, rn);
                    queryBuilders.put(operator.isSign(),
                            QueryBuilders.rangeQuery(fieldName).from(range.lowerEndpoint())
                                    .to(range.upperEndpoint()).includeLower(true).includeUpper(true));
                    return;
                } catch (Exception nfe) {
                    throw new IllegalArgumentException("format incorrect: field=" + fieldName + ", value=" + fieldValue,
                            nfe);
                }
            } else if (!"*".equals(left)) {
                Comparable<? extends Number> ln = parse(clazz, left);
                queryBuilders.put(operator.isSign(),
                        QueryBuilders.rangeQuery(fieldName).gte(ln));
                return;
            } else if (!"*".equals(right)) {
                Comparable<? extends Number> rn = parse(clazz, right);
                queryBuilders.put(operator.isSign(),
                        QueryBuilders.rangeQuery(fieldName).lte(rn));
                return;
            }
        }
        throw new IllegalArgumentException("format incorrect: field=" + fieldName + ", value=" + fieldValue);
    }

    protected Comparable<? extends Number> parse(String clazz, String str) {
        return Enums.getIfPresent(Parsers.class, clazz).or(Parsers.Long).parse(str);
    }

    enum Parsers {
        Byte {
            @Override
            Comparable<? extends Number> parse(String str) {
                try {
                    return java.lang.Byte.parseByte(str);
                } catch (NumberFormatException nfe) {
                    return null;
                }
            }
        },
        Short {
            @Override
            Comparable<? extends Number> parse(String str) {
                try {
                    return java.lang.Short.parseShort(str);
                } catch (NumberFormatException nfe) {
                    return null;
                }
            }
        },
        Integer {
            @Override
            Comparable<? extends Number> parse(String str) {
                return Ints.tryParse(str);
            }
        },
        Long {
            @Override
            Comparable<? extends Number> parse(String str) {
                return Longs.tryParse(str);
            }
        };

        abstract Comparable<? extends Number> parse(String str);

    }

}
